package com.ponhu.ea2.stockcenter.provider.service.rpc.impl;

import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.ponhu.ea2.common.basic.model.PagedResult;
import com.ponhu.ea2.stockcenter.dto.StockDiffQueryDto;
import com.ponhu.ea2.stockcenter.provider.entity.*;
import com.ponhu.ea2.stockcenter.provider.mapper.TemStockDifferenceMapper;
import com.ponhu.ea2.stockcenter.provider.service.biz.stockinit.GWallStockDataService;
import com.ponhu.ea2.stockcenter.provider.service.entity.*;
import com.ponhu.ea2.stockcenter.provider.support.exception.StockRuntimeException;
import com.ponhu.ea2.stockcenter.provider.support.service.RabbitMQService;
import com.ponhu.ea2.stockcenter.provider.utils.DingTalkHandler;
import com.ponhu.ea2.stockcenter.provider.utils.ProBeanUtils;
import com.ponhu.ea2.stockcenter.provider.utils.QiNiuUploadUtils;
import com.ponhu.ea2.stockcenter.service.bms.StockCheckService;
import com.ponhu.ea2.stockcenter.service.bms.StockFixService;
import com.ponhu.ea2.stockcenter.vo.DingTalkContentVO;
import com.ponhu.ea2.stockcenter.vo.ReturnResponseBean;
import com.ponhu.ea2.stockcenter.vo.StockDiffUploadLogsVo;
import com.ponhu.ea2.stockcenter.vo.TemStockDifferenceVo;
import com.ponhu.ea2.wmscenter.model.bms.vo.GoodsStockInfoVo;
import io.netty.util.concurrent.FastThreadLocal;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static com.ponhu.ea2.stockcenter.enums.ResponseCodeEnum.*;

@Slf4j
@DubboService(version = "1.0.0", interfaceClass = StockCheckService.class, timeout = 60000)
public class StockCheckServiceImpl implements StockCheckService {

    @Resource
    private DingTalkHandler dingTalkHandler;

    @Autowired
    private FastThreadLocal<String> traceIdLocal;

    @Autowired
    private TemStockDifferenceMapper temStockDifferenceMapper;

    @Autowired
    private StockFixService stockFixService;
    @Autowired
    private RabbitMQService rabbitMQService;
    @Autowired
    private GWallDataService gWallDataService;
    @Autowired
    private GWallStockService gWallStockService;
    @Autowired
    private GWallStockDataService gWallStockDataService;
    @Autowired
    private StockWarehouseService stockWarehouseService;
    @Autowired
    protected StockInventoryRealService stockInventoryRealService;
    @Autowired
    private StockDiffUploadLogsService phStockDiffUploadLogsService;
    @Autowired
    protected StockWarehouseRelationService stockWarehouseRelationService;

    @Override
    public void initTemStockDifferenceService() {

        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> temStockDifferenceMapper.insertIntoDifference1());
        CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> temStockDifferenceMapper.insertIntoDifference2());
        CompletableFuture<Void> future3 = CompletableFuture.runAsync(() -> temStockDifferenceMapper.insertIntoDifference3());
        CompletableFuture<Void> future4 = CompletableFuture.runAsync(() -> temStockDifferenceMapper.insertIntoDifference4());

        CompletableFuture.allOf(future1, future2, future3, future4).join();
    }

    /**
     * 定时对比库存中心和巨沃货品库存数量：type 1-通过巨沃接口，2-通过巨沃数据库
     *
     * @param type 1-通过巨沃接口，2-通过巨沃数据库
     * @return
     */
    @Override
    public ReturnResponseBean<List<TemStockDifferenceVo>> exportStockDifferenceVos(Integer type) {
        log.info("exportStockDifferenceVos ===>> 定时对比库存中心和巨沃货品库存数量 ===>> 开始");

        // 查询库存中心维护的所有仓库信息
        List<StockWarehouse> warehouseList = stockWarehouseService.list();

        // 遍历所有仓库信息依次对比指定仓库下的所有货品库存数量
        List<TemStockDifferenceVo> stockDifferenceVos = warehouseList.parallelStream().flatMap(warehouse -> {

            // 查询巨沃指定仓库的货品库存信息
            List<GWallWareHouseStock> stockInfoVos;
            if (type == 1) stockInfoVos = gWallStockDataService
                    .getGWallStockDataNew(warehouse.getWarehouseCode(), null).parallelStream()
                    .filter(stockInfo -> StringUtils.isEmpty(stockInfo.getGwf1())) // 巨沃货品库存无效数据
                    .filter(stockInfo -> ObjectUtils.isNotEmpty(stockInfo.getQty()) && stockInfo.getQty() > 0)
                    .map(stockInfo -> new GWallWareHouseStock().setInventoryCode(stockInfo.getSku())
                            .setWarehouseCode(stockInfo.getWarehouseCode()).setStockNum(stockInfo.getQty()))
                    .collect(Collectors.toList());
            else stockInfoVos = gWallStockService.getGWallStockList(warehouse.getWarehouseCode(), null);
            log.info("exportStockDifferenceVos ===>> 查询巨沃指定仓库的货品库存信息 ===>> {}：{} ===>> 查询数量：{}",
                    warehouse.getWarehouseName(), warehouse.getWarehouseCode(), stockInfoVos.size());

            // 查询库存中心指定仓库的货品库存信息
            List<StockWarehouseRelation> warehouseRelations = stockWarehouseRelationService
                    .getRelationsByWarehouseCode(warehouse.getWarehouseCode(), true);
            log.info("exportStockDifferenceVos ===>> 查询库存中心指定仓库的货品库存信息 ===>> {}：{} ===>> 查询数量：{}",
                    warehouse.getWarehouseName(), warehouse.getWarehouseCode(), warehouseRelations.size());

            List<TemStockDifferenceVo> differenceVoList = Lists.newArrayList();
            stockInfoVos = stockInfoVos.parallelStream()
                    .filter(stock -> stock.getStockNum() > 0).collect(Collectors.toList());

            // 校验对比库存中心存在&巨沃存在&库存数量不一致的情况
            // 校验对比库存中心不存在&巨沃存在的情况
            if (CollectionUtils.isNotEmpty(stockInfoVos)) {
                Map<String, Integer> reationMap = warehouseRelations.parallelStream().collect(Collectors
                        .toMap(StockWarehouseRelation::getInventoryCode, StockWarehouseRelation::getRealStockNum, (k1, k2) -> k1));

                // 遍历指定仓库下巨沃存在的货品库存信息，找出库存中心存在且库存数量不一致的或库存中心不存在的
                List<TemStockDifferenceVo> differenceVoList1 = stockInfoVos.parallelStream().map(stockInfo -> {

                    Integer realStockNum = reationMap.getOrDefault(stockInfo.getInventoryCode(), -1);
                    // 库存中心存在&巨沃存在&库存数量一致的情况
                    if (stockInfo.getStockNum().equals(realStockNum)) return null;

                    TemStockDifferenceVo differenceVo = new TemStockDifferenceVo()
                            .setInventoryCode(stockInfo.getInventoryCode())
                            .setWarehouseCode(stockInfo.getWarehouseCode())
                            .setJwHas("存在")
                            .setJwStockNum(stockInfo.getStockNum());
                    // 校验对比库存中心不存在&巨沃存在的情况
                    if (realStockNum == -1) return differenceVo.setWhHas("不存在");
                    // 校验对比库存中心存在&巨沃存在&库存数量不一致的情况
                    return differenceVo.setWhHas("存在").setWhStockNum(realStockNum);
                }).filter(ObjectUtils::isNotEmpty).collect(Collectors.toList());

                differenceVoList.addAll(differenceVoList1);
            }

            // 校验对比库存中心存在&巨沃不存在的情况
            Set<String> jwInventoryCodeSet = stockInfoVos.parallelStream()
                    .map(GWallWareHouseStock::getInventoryCode).collect(Collectors.toSet());
            List<TemStockDifferenceVo> differenceVoList2 = warehouseRelations.parallelStream()
                    .filter(relation -> !jwInventoryCodeSet.contains(relation.getInventoryCode()))
                    .map(relation -> new TemStockDifferenceVo()
                            .setInventoryCode(relation.getInventoryCode())
                            .setWarehouseCode(relation.getWarehouseCode())
                            .setJwHas("不存在")
                            .setWhHas("存在")
                            .setWhStockNum(relation.getRealStockNum())).collect(Collectors.toList());
            differenceVoList.addAll(differenceVoList2);

            return differenceVoList.stream();
        }).collect(Collectors.toList());

        // 查询库存中心货品档案列表(即销售库存表)用于校验商品系统不存在的情况
        List<String> inventoryCodeList = stockDifferenceVos.parallelStream()
                .filter(differenceVo -> differenceVo.getWhHas().equals("不存在"))
                .map(TemStockDifferenceVo::getInventoryCode).collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(inventoryCodeList)) {
            Set<String> realSet = stockInventoryRealService.getRealInventoryStockListIfNullCreate(inventoryCodeList)
                    .parallelStream().map(StockInventoryReal::getInventoryCode).collect(Collectors.toSet());

            // 校验对比商品系统不存在&巨沃存在的情况
            stockDifferenceVos = stockDifferenceVos.parallelStream().map(differenceVo -> {
                if (!differenceVo.getWhHas().equals("不存在")) return differenceVo;
                else if (realSet.contains(differenceVo.getInventoryCode())) return differenceVo;
                else return differenceVo.setWhHas("商品系统不存在");
            }).collect(Collectors.toList());
        }

        log.info("exportStockDifferenceVos ===>> 定时对比库存中心和巨沃货品库存数量 ===>> 完成 " +
                "===>> 差异数量：{}", stockDifferenceVos.size());

        return ReturnResponseBean.ok(stockDifferenceVos);
    }

    @Override
    public ReturnResponseBean<List<String>> correctStockByInventoryCodes(Collection<String> inventoryCodes, Integer type) {

        List<String> realInventoryCodes = Lists.newArrayList();

        ReturnResponseBean<List<String>> returnResponseBean = ((StockCheckServiceImpl) AopContext.currentProxy())
                .correctStockByInventoryCodes_DSTransactional(inventoryCodes, type, realInventoryCodes);

        if (CollectionUtils.isNotEmpty(realInventoryCodes))
            rabbitMQService.saleStockChangeSendMsg(realInventoryCodes);

        return returnResponseBean;
    }

    /**
     * 定时修复货品库存数量：type 1-通过巨沃接口，2-通过巨沃数据库
     *
     * @param inventoryCodes
     * @param type           1-通过巨沃接口，2-通过巨沃数据库
     * @return
     */
    @DSTransactional
    public ReturnResponseBean<List<String>> correctStockByInventoryCodes_DSTransactional(
            Collection<String> inventoryCodes, Integer type, List<String> realInventoryCodes) {

        log.info("correctStockByInventoryCodes ===>> 修复库存开始 ===>> {} " +
                "===>> 入参={}", traceIdLocal.get(), inventoryCodes);

        // 过滤参数
        List<String> inventoryCodeList = inventoryCodes
                .parallelStream().filter(StringUtils::isNotEmpty).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(inventoryCodeList)) {
            log.info("correctStockByInventoryCodes ===>> 修复库存完成[1] ===>> {} " +
                    "===>> 入参为空", traceIdLocal.get());
            return ReturnResponseBean.ok(inventoryCodeList);
        }

        // 巨沃查询出来的库存数据
        List<GoodsStockInfoVo> gwStockInfoVos;
        if (type == 1) gwStockInfoVos = gWallStockDataService
                .getGWallStockDataNew(null, inventoryCodeList);
        else gwStockInfoVos = gWallStockService.getGWallStockList(null, inventoryCodeList)
                .parallelStream().map(stockInfo -> {
                    GoodsStockInfoVo goodsStockInfoVo = new GoodsStockInfoVo();
                    goodsStockInfoVo.setQty(stockInfo.getStockNum());
                    goodsStockInfoVo.setSku(stockInfo.getInventoryCode());
                    goodsStockInfoVo.setWarehouseCode(stockInfo.getWarehouseCode());
                    return goodsStockInfoVo;
                }).collect(Collectors.toList());
        log.info("correctStockByInventoryCodes ===>> 修复库存查询巨沃库存信息 ===>> {} ===>> " +
                "入参={} ===>> 巨沃={}", traceIdLocal.get(), inventoryCodeList, gwStockInfoVos);

        // key-查询成功或失败，value-巨沃库存数据
        Map<Boolean, List<GoodsStockInfoVo>> stockInfoVoMap = gwStockInfoVos.parallelStream().collect(Collectors
                .partitioningBy(stockInfoVo -> !Objects.toString(stockInfoVo.getGwf1(), "").contains("超过了限定的次数")));

        // 巨沃查询错误的库存信息，其中”超过了限定的次数“判定为查询失败，其它异常情况判断为查询成功且库存数为0
        List<String> failList = stockInfoVoMap.getOrDefault(false, Lists.newArrayList()).parallelStream()
                .map(stockInfoVo -> String.format("%s\t%s", stockInfoVo.getSku(), stockInfoVo.getGwf1()))
                .collect(Collectors.toList());
        if (failList.size() == inventoryCodeList.size()) {
            log.info("correctStockByInventoryCodes ===>> 修复库存完成[2] ===>> {} ===>> " +
                    "入参={} ===>> 其中执行失败={}", traceIdLocal.get(), inventoryCodeList, failList);
            return ReturnResponseBean.ok(failList);
        }

        // 巨沃查询出来的有效库存数据
        List<GoodsStockInfoVo> stockInfoVos = stockInfoVoMap.get(true);

        Set<String> removeInventoryCodes = Sets.newHashSet();
        removeInventoryCodes.addAll(stockInfoVos.parallelStream().map(GoodsStockInfoVo::getSku).collect(Collectors.toSet()));
        if (type == 2) {
            Set<String> gwSkus = gwStockInfoVos.parallelStream().map(GoodsStockInfoVo::getSku).collect(Collectors.toSet());
            removeInventoryCodes.addAll(inventoryCodes.parallelStream()
                    .filter(inventoryCode -> !gwSkus.contains(inventoryCode)).collect(Collectors.toSet()));
        }

        // 删除库存中心旧的库存数据
        LambdaQueryWrapper<StockWarehouseRelation> queryWrapper = Wrappers
                .lambdaQuery(StockWarehouseRelation.class)
                .in(StockWarehouseRelation::getInventoryCode, removeInventoryCodes);
        stockWarehouseRelationService.remove(queryWrapper);

        // 查询出要修复货品的销售侧库存数据
        List<StockInventoryReal> stockReals = stockInventoryRealService.getRealInventoryStockListIfNullCreate(inventoryCodeList);

        // key-货品编码，value-货品ID
        Map<String, Long> inventoryMap = stockReals.parallelStream().collect(Collectors
                .toMap(StockInventoryReal::getInventoryCode, StockInventoryReal::getInventoryId));

        // 根据巨沃擦查询结果新生成相关货品实物侧库存数据
        List<StockWarehouseRelation> correctStockReations = stockInfoVos.parallelStream()
                .filter(stockInfo -> StringUtils.isEmpty(stockInfo.getGwf1()) && stockInfo.getQty() > 0)
                .map(stockInfo -> new StockWarehouseRelation()
                        .setInventoryId(inventoryMap.get(stockInfo.getSku()))
                        .setInventoryCode(stockInfo.getSku())
                        .setWarehouseCode(stockInfo.getWarehouseCode())
                        .setRealStockNum(stockInfo.getQty())
                        .setAvailableStockNum(stockInfo.getQty())
                ).filter(stock -> ObjectUtils.isNotEmpty(stock.getInventoryId()))
                .collect(Collectors.toList());

        // 根据巨沃擦查询结果新增相关货品实物侧库存数据
        if (CollectionUtils.isNotEmpty(correctStockReations))
            if (!stockWarehouseRelationService.saveBatch(correctStockReations))
                throw new StockRuntimeException(STOCK_CHECK_EXCEPTION_900402);

        // 查询仓库信息并筛选出可售仓
        Set<String> saleWhs = stockWarehouseService.list().parallelStream()
                .filter(wh -> wh.getSaleStatus()).map(StockWarehouse::getWarehouseCode)
                .collect(Collectors.toSet());

        // key-货品编码，value-货品可售库存数
        Map<String, Integer> saleStockMap = stockInfoVos.parallelStream()
                .filter(stockInfo -> saleWhs.contains(stockInfo.getWarehouseCode()))
                .collect(Collectors.groupingBy(GoodsStockInfoVo::getSku,
                        Collectors.mapping(GoodsStockInfoVo::getQty,
                                Collectors.reducing(0, Integer::sum))));

        // 查询巨沃货品调拨在途库存数
        Map<String, Integer> freeMap = gWallDataService.getFreeStockList(inventoryCodeList).parallelStream()
                .collect(Collectors.groupingBy(GWallApplyFreeStock::getInventoryCode,
                        Collectors.mapping(GWallApplyFreeStock::getFreeNum,
                                Collectors.reducing(0, Integer::sum))));
        log.info("correctStockByInventoryCodes ===>> 查询巨沃货品调拨在途库存数 ===>> {} ===>> " +
                "入参={} ===>> 巨沃={}", traceIdLocal.get(), inventoryCodeList, freeMap);

        // 修复相关货品可售库存数
        stockReals.parallelStream().forEach(stockReal ->
                stockReal.setFreeStockNum(freeMap.get(stockReal.getInventoryCode()))
                        .setSaleStockNum(saleStockMap.getOrDefault(stockReal.getInventoryCode(), 0)));
        if (CollectionUtils.isNotEmpty(stockReals)) {
            realInventoryCodes.addAll(stockReals.stream().map(StockInventoryReal::getInventoryCode).collect(Collectors.toList()));

            if (!stockInventoryRealService.saveOrUpdateBatch(stockReals))
                throw new StockRuntimeException(STOCK_CHECK_EXCEPTION_900403);

            stockFixService.fixSalesUsedStockNum(stockReals.parallelStream()
                    .map(StockInventoryReal::getInventoryCode).collect(Collectors.toList()));
        }

        log.info("correctStockByInventoryCodes ===>> 修复库存完成[3] ===>> {} ===>> " +
                "入参={} ===>> 其中执行失败={}", traceIdLocal.get(), inventoryCodeList, failList);

        //发送钉钉提醒
        sendDingTalk(inventoryCodes, traceIdLocal.get());

        // 返回修改失败的货品编码
        return ReturnResponseBean.ok(failList);
    }

    private void sendDingTalk(Collection<String> inventoryCodes, String traceId) {
        CompletableFuture.runAsync(() -> {
            DingTalkContentVO dingTalkContentVO = new DingTalkContentVO();
            dingTalkContentVO.setTitle("手工修复库存汇总");
            dingTalkContentVO.setPath("StockCheckServiceImpl.correctStockByInventoryCodes");
            dingTalkContentVO.setTraceId(traceId);
            dingTalkContentVO.addDesc("请求参数", JSONObject.toJSONString(inventoryCodes));

            dingTalkHandler.sendMarkDownMsg(dingTalkContentVO);
        });
    }

    @Override
    public void diffUpload(List<TemStockDifferenceVo> differenceVoList) {
        CompletableFuture.runAsync(() -> {
            if (CollectionUtils.isNotEmpty(differenceVoList)) {
                log.info("StockCheckService#diffUpload ===>> 库存差异文件上传七牛云开始 " +
                        "===>> differenceVoList={}", JSON.toJSONString(differenceVoList));
                ByteArrayOutputStream bos = new ByteArrayOutputStream(); //字节流
                try {
                    String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("YYYYMMddHHmmss"));
                    String fileName = "库存中心和巨沃差异对比统计" + time + ".xlsx";
                    ExcelWriter excelWriter = EasyExcelFactory.write(bos, TemStockDifferenceVo.class).build();
                    WriteSheet sheet1 = EasyExcelFactory.writerSheet(1, "巨沃和库存中心都存在").build();
                    WriteSheet sheet2 = EasyExcelFactory.writerSheet(2, "库存中心存在&巨沃不存在").build();
                    WriteSheet sheet3 = EasyExcelFactory.writerSheet(3, "巨沃存在&库存中心不存在").build();
                    WriteSheet sheet4 = EasyExcelFactory.writerSheet(4, "巨沃存在&商品系统不存在").build();

                    excelWriter.write(differenceVoList.parallelStream().filter(differenceVo -> differenceVo.getWhHas().equals("存在")
                            && differenceVo.getJwHas().equals("存在")).collect(Collectors.toList()), sheet1);
                    excelWriter.write(differenceVoList.parallelStream().filter(differenceVo -> differenceVo.getWhHas().equals("存在")
                            && differenceVo.getJwHas().equals("不存在")).collect(Collectors.toList()), sheet2);
                    excelWriter.write(differenceVoList.parallelStream().filter(differenceVo -> differenceVo.getJwHas().equals("存在")
                            && differenceVo.getWhHas().equals("不存在")).collect(Collectors.toList()), sheet3);
                    excelWriter.write(differenceVoList.parallelStream().filter(differenceVo -> differenceVo.getJwHas().equals("存在")
                            && differenceVo.getWhHas().equals("商品系统不存在")).collect(Collectors.toList()), sheet4);
                    excelWriter.finish();
                    String uploadUrl = QiNiuUploadUtils.uploadExcelFile(bos, fileName);
                    log.info("StockCheckService#diffUpload ===>> 库存差异文件上传七牛云完成 ===>> uploadUrl={}", uploadUrl);

                    StockDiffUploadLogs phStockDiffUploadLogs = new StockDiffUploadLogs()
                            .setDiffNum(differenceVoList.size())
                            .setUploadUrl(uploadUrl);

                    phStockDiffUploadLogsService.saveOrUpdate(phStockDiffUploadLogs);
                } catch (Exception e) {
                    e.printStackTrace();
                    log.info("StockCheckService#diffUpload ===>> 库存差异文件上传七牛云程序异常");
                } finally {
                    try {
                        bos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    @Override
    public PagedResult<StockDiffUploadLogsVo> getAllStockDiffUploadLogs(StockDiffQueryDto stockDiffQueryDto) {

        IPage<StockDiffUploadLogs> iPage = phStockDiffUploadLogsService.getPageByCreateTime(
                stockDiffQueryDto.getCreateTime(), stockDiffQueryDto.getPageIndex(), stockDiffQueryDto.getPageSize());

        List<StockDiffUploadLogsVo> stockDiffVoList = iPage.getRecords().parallelStream()
                .map(diff -> ProBeanUtils.copyProperties(diff, StockDiffUploadLogsVo.class)).collect(Collectors.toList());

        return new PagedResult<>(stockDiffVoList, (int) iPage.getCurrent(), iPage.getTotal(), (int) iPage.getSize());
    }

}

